﻿namespace mathD
{
    export class Matrix
    {
        public rawData: Float32Array;
        constructor(datas: Float32Array = null)
        {
            if (datas)
            {
                this.rawData = datas;
            }
            else
                this.rawData = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        }

        toString(): string
        {
            return "[" + this.rawData[0] + "," + this.rawData[1] + "," + this.rawData[2] + "," + this.rawData[3] + "],"
                + "[" + this.rawData[4] + "," + this.rawData[5] + "," + this.rawData[6] + "," + this.rawData[7] + "],"
                + "[" + this.rawData[8] + "," + this.rawData[9] + "," + this.rawData[10] + "," + this.rawData[11] + "],"
                + "[" + this.rawData[12] + "," + this.rawData[13] + "," + this.rawData[14] + "," + this.rawData[15] + "]";
        }

        static clone(src: Matrix, out: Matrix)
        {
            for (var i = 0; i < 16; i++)
            {
                out.rawData[i] = src.rawData[i];
            }
        }

        static identity(out: Matrix)
        {
            out.rawData.set(
                [
                    1.0,0,0,0,
                    0,1.0,0,0,
                    0,0,1.0,0,
                    0,0,0,1.0
                ]
            );
        }
        static toZero(out: Matrix)
        {
            out.rawData.set(
                [
                    0,0,0,0,
                    0,0,0,0,
                    0,0,0,0,
                    0,0,0,1.0
                ]
            );
        }
        
        static getTranslation(src: Matrix, translate: Vector3)
        {
            translate.x = src.rawData[12];
            translate.y = src.rawData[13];
            translate.z = src.rawData[14];
        }
        static getScale(src:Matrix, scale:Vector3):void
        {
            scale.x = src.rawData[0];
            scale.y = src.rawData[5];
            scale.z = src.rawData[10];
        }

        /**
        * @language zh_CN
        * 当前矩阵转置
        * @version gd3d 1.0
        * @platform Web,Native
        */
        static transpose(src: Matrix, out: Matrix)
        {
            out.rawData[1] = src.rawData[4];
            out.rawData[2] = src.rawData[8];
            out.rawData[3] = src.rawData[12];
            out.rawData[4] = src.rawData[1];
            out.rawData[6] = src.rawData[9];
            out.rawData[7] = src.rawData[13];
            out.rawData[8] = src.rawData[2];
            out.rawData[9] = src.rawData[6];
            out.rawData[11] = src.rawData[14];
            out.rawData[12] = src.rawData[3];
            out.rawData[13] = src.rawData[7];
            out.rawData[14] = src.rawData[11];
        }


        static decompose(src: Matrix, scale: Vector3, rotation: Quaternion, translation: Vector3): boolean
        {
            translation.x = src.rawData[12];
            translation.y = src.rawData[13];
            translation.z = src.rawData[14];

            var xs = sign(src.rawData[0] * src.rawData[1] * src.rawData[2] * src.rawData[3]) < 0 ? -1 : 1;
            var ys = sign(src.rawData[4] * src.rawData[5] * src.rawData[6] * src.rawData[7]) < 0 ? -1 : 1;
            var zs = sign(src.rawData[8] * src.rawData[9] * src.rawData[10] * src.rawData[11]) < 0 ? -1 : 1;

            scale.x = xs * Math.sqrt(src.rawData[0] * src.rawData[0] + src.rawData[1] * src.rawData[1] + src.rawData[2] * src.rawData[2]);
            scale.y = ys * Math.sqrt(src.rawData[4] * src.rawData[4] + src.rawData[5] * src.rawData[5] + src.rawData[6] * src.rawData[6]);
            scale.z = zs * Math.sqrt(src.rawData[8] * src.rawData[8] + src.rawData[9] * src.rawData[9] + src.rawData[10] * src.rawData[10]);

            if (scale.x === 0 || scale.y === 0 || scale.z === 0)
            {
                rotation.x = 0;
                rotation.y = 0;
                rotation.z = 0;
                rotation.w = 1;
                return false;
            }

            var mat = pool.new_matrix();
            mat.rawData[0] = src.rawData[0] / scale.x;
            mat.rawData[1] = src.rawData[1] / scale.x;
            mat.rawData[2] = src.rawData[2] / scale.x;
            mat.rawData[3] = 0;

            mat.rawData[4] = src.rawData[4] / scale.y;
            mat.rawData[5] = src.rawData[5] / scale.y;
            mat.rawData[6] = src.rawData[6] / scale.y;
            mat.rawData[7] = 0;

            mat.rawData[8] = src.rawData[8] / scale.z;
            mat.rawData[9] = src.rawData[9] / scale.z;
            mat.rawData[10] = src.rawData[10] / scale.z;
            mat.rawData[11] = 0;

            Matrix3x2.Quaternion(mat, rotation);
            return true;
        }


        static inverse(src: Matrix, out: Matrix)
        {
            var l1 = src.rawData[0],l2 = src.rawData[1],l3 = src.rawData[2],l4 = src.rawData[3];
            var l5 = src.rawData[4],l6 = src.rawData[5],l7 = src.rawData[6],l8 = src.rawData[7];
            var l9 = src.rawData[8],l10 = src.rawData[9],l11 = src.rawData[10],l12 = src.rawData[11];
            var l13 = src.rawData[12],l14 = src.rawData[13],l15 = src.rawData[14],l16 = src.rawData[15];
            var l17 = (l11 * l16) - (l12 * l15);
            var l18 = (l10 * l16) - (l12 * l14);
            var l19 = (l10 * l15) - (l11 * l14);
            var l20 = (l9 * l16) - (l12 * l13);
            var l21 = (l9 * l15) - (l11 * l13);
            var l22 = (l9 * l14) - (l10 * l13);
            var l23 = ((l6 * l17) - (l7 * l18)) + (l8 * l19);
            var l24 = -(((l5 * l17) - (l7 * l20)) + (l8 * l21));
            var l25 = ((l5 * l18) - (l6 * l20)) + (l8 * l22);
            var l26 = -(((l5 * l19) - (l6 * l21)) + (l7 * l22));
            var l27 = 1.0 / ((((l1 * l23) + (l2 * l24)) + (l3 * l25)) + (l4 * l26));
            var l28 = (l7 * l16) - (l8 * l15);
            var l29 = (l6 * l16) - (l8 * l14);
            var l30 = (l6 * l15) - (l7 * l14);
            var l31 = (l5 * l16) - (l8 * l13);
            var l32 = (l5 * l15) - (l7 * l13);
            var l33 = (l5 * l14) - (l6 * l13);
            var l34 = (l7 * l12) - (l8 * l11);
            var l35 = (l6 * l12) - (l8 * l10);
            var l36 = (l6 * l11) - (l7 * l10);
            var l37 = (l5 * l12) - (l8 * l9);
            var l38 = (l5 * l11) - (l7 * l9);
            var l39 = (l5 * l10) - (l6 * l9);

            out.rawData[0] = l23 * l27;
            out.rawData[4] = l24 * l27;
            out.rawData[8] = l25 * l27;
            out.rawData[12] = l26 * l27;
            out.rawData[1] = -(((l2 * l17) - (l3 * l18)) + (l4 * l19)) * l27;
            out.rawData[5] = (((l1 * l17) - (l3 * l20)) + (l4 * l21)) * l27;
            out.rawData[9] = -(((l1 * l18) - (l2 * l20)) + (l4 * l22)) * l27;
            out.rawData[13] = (((l1 * l19) - (l2 * l21)) + (l3 * l22)) * l27;
            out.rawData[2] = (((l2 * l28) - (l3 * l29)) + (l4 * l30)) * l27;
            out.rawData[6] = -(((l1 * l28) - (l3 * l31)) + (l4 * l32)) * l27;
            out.rawData[10] = (((l1 * l29) - (l2 * l31)) + (l4 * l33)) * l27;
            out.rawData[14] = -(((l1 * l30) - (l2 * l32)) + (l3 * l33)) * l27;
            out.rawData[3] = -(((l2 * l34) - (l3 * l35)) + (l4 * l36)) * l27;
            out.rawData[7] = (((l1 * l34) - (l3 * l37)) + (l4 * l38)) * l27;
            out.rawData[11] = -(((l1 * l35) - (l2 * l37)) + (l4 * l39)) * l27;
            out.rawData[15] = (((l1 * l36) - (l2 * l38)) + (l3 * l39)) * l27;
        }

        static makeTransformRTS(pos: Vector3, scale: Vector3, rot: Quaternion, out: Matrix)
        {
            var matS = pool.new_matrix();
            this.makeScale(scale.x, scale.y, scale.z, matS);
            var matR = pool.new_matrix();
            Quaternion.toMatrix(rot, matR);
            this.multiply(matR, matS, out);

            out.rawData[12] = pos.x;
            out.rawData[13] = pos.y;
            out.rawData[14] = pos.z;
            out.rawData[15] = 1;

            pool.delete_matrix(matS);
            pool.delete_matrix(matR);
        }
        static makeTranslate(x: number, y: number, z: number, out: Matrix): void
        {
            out.rawData.set(
                [
                    1.0,0,0,0,
                    0,1.0,0,0,
                    0,0,1.0,0,
                    x,y,z,1.0
                ]);
        }

        static makeScale(xScale: number, yScale: number, zScale: number, out: Matrix): void
        {

            out.rawData[0] = xScale; out.rawData[1] = 0.0; out.rawData[2] = 0.0; out.rawData[3] = 0.0;
            out.rawData[4] = 0.0; out.rawData[5] = yScale; out.rawData[6] = 0.0; out.rawData[7] = 0.0;
            out.rawData[8] = 0.0; out.rawData[9] = 0.0; out.rawData[10] = zScale; out.rawData[11] = 0.0;
            out.rawData[12] = 0.0; out.rawData[13] = 0.0; out.rawData[14] = 0.0; out.rawData[15] = 1.0;
        }

        static makeRotateAxisAngle(axis: Vector3, angle: number, out: Matrix)
        {
            var x = axis.x,
                y = axis.y,
                z = axis.z;

            var length = Math.sqrt(x * x + y * y + z * z);

            if (!length)
                return;

            if (length !== 1)
            {
                length = 1 / length;
                x *= length;
                y *= length;
                z *= length;
            }

            var s = Math.sin(angle);
            var c = Math.cos(angle);

            var t = 1.0 - c;


            var b00 = x * x * t + c, b01 = y * x * t + z * s, b02 = z * x * t - y * s,
                b10 = x * y * t - z * s, b11 = y * y * t + c, b12 = z * y * t + x * s,
                b20 = x * z * t + y * s, b21 = y * z * t - x * s, b22 = z * z * t + c;
            out.rawData.set(
                [
                    b00,b01,b02,0,
                    b10,b11,b12,0,
                    b20,b21,b22,0,
                    0,0,0,1
                ]
            );

        }
        static multiply(lhs: Matrix, rhs: Matrix, out: Matrix): void
        {
            var a00 = lhs.rawData[0], a01 = lhs.rawData[1], a02 = lhs.rawData[2], a03 = lhs.rawData[3];
            var a10 = lhs.rawData[4], a11 = lhs.rawData[5], a12 = lhs.rawData[6], a13 = lhs.rawData[7];
            var a20 = lhs.rawData[8], a21 = lhs.rawData[9], a22 = lhs.rawData[10], a23 = lhs.rawData[11];
            var a30 = lhs.rawData[12], a31 = lhs.rawData[13], a32 = lhs.rawData[14], a33 = lhs.rawData[15];

            var b0 = rhs.rawData[0],
                b1 = rhs.rawData[1],
                b2 = rhs.rawData[2],
                b3 = rhs.rawData[3];

            out.rawData[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
            out.rawData[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
            out.rawData[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
            out.rawData[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;

            b0 = rhs.rawData[4];
            b1 = rhs.rawData[5];
            b2 = rhs.rawData[6];
            b3 = rhs.rawData[7];

            out.rawData[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
            out.rawData[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
            out.rawData[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
            out.rawData[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;

            b0 = rhs.rawData[8];
            b1 = rhs.rawData[9];
            b2 = rhs.rawData[10];
            b3 = rhs.rawData[11];

            out.rawData[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
            out.rawData[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
            out.rawData[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
            out.rawData[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;

            b0 = rhs.rawData[12];
            b1 = rhs.rawData[13];
            b2 = rhs.rawData[14];
            b3 = rhs.rawData[15];

            out.rawData[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
            out.rawData[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
            out.rawData[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
            out.rawData[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
        }
        static project_PerspectiveLH(fov: number, aspect: number, znear: number, zfar: number, out: Matrix)
        {
            var tan = 1.0 / (Math.tan(fov * 0.5));
            out.rawData[0] = tan / aspect;
            out.rawData[1] = out.rawData[2] = out.rawData[3] = 0.0;

            out.rawData[4] = out.rawData[6] = out.rawData[7] = 0.0;
            out.rawData[5] = tan;

            out.rawData[8] = out.rawData[9] = 0.0;
            out.rawData[10] = -zfar / (znear - zfar);
            out.rawData[11] = 1.0;

            out.rawData[12] = out.rawData[13] = out.rawData[15] = 0.0;
            out.rawData[14] = (znear * zfar) / (znear - zfar);
        }
        static project_OrthoLH(width: number, height: number, znear: number, zfar: number, out: Matrix)
        {
            var hw = 2.0 / width;
            var hh = 2.0 / height;
            var id = 1.0 / (zfar - znear);
            var nid = znear / (znear - zfar);
            out.rawData.set(
                [
                    hw,0,0,0,
                    0,hh,0,0,
                    0,0,id,0,
                    0,0,nid,1
                ]
            );
        }
        //lights fix
        static lookatLH(forward: Vector3, up: Vector3, out: Matrix)
        {
            // Z axis
            var z = new Vector3(-forward.x, -forward.y, -forward.z);
            Vector3.normalize(z, z);

            var y = new Vector3();
            Vector3.clone(up, y);
            Vector3.normalize(y, y);


            // X axis
            var x = new Vector3();
            Vector3.cross(y, z, x);
            Vector3.SqrLength(x)
            if (Vector3.SqrLength(x) == 0)
            {
                x.x = 1;
            }
            else
            {
                Vector3.normalize(x, x);
            }
            // Y axis
            var y = new Vector3();
            Vector3.cross(z, x, y);
            Vector3.normalize(y, y);
            out.rawData.set(
                [
                    x.x,y.x,z.x,0,
                    x.y,y.y,z.y,0,
                    x.z,y.z,z.z,0,
                    0,0,0,1
                ]
            );
        }
        static viewLookatLH(eye: Vector3, forward: Vector3, up: Vector3, out: Matrix)
        {
            // Z axis
            var z = new Vector3(forward.x, forward.y, forward.z);
            Vector3.normalize(z, z);

            var y = new Vector3();
            Vector3.clone(up, y);
            Vector3.normalize(y, y);
            // X axis
            var x = new Vector3();
            Vector3.cross(y, z, x);
            Vector3.SqrLength(x)
            if (Vector3.SqrLength(x) == 0)
            {
                x.x = 1;
            }
            else
            {
                Vector3.normalize(x, x);
            }
            // Y axis
            var y = new Vector3();
            Vector3.cross(z, x, y);
            Vector3.normalize(y, y);
            // Eye angles
            var ex = -Vector3.dot(x, eye);
            var ey = -Vector3.dot(y, eye);
            var ez = -Vector3.dot(z, eye);
            out.rawData.set(
                [
                    x.x,y.x,z.x,0,
                    x.y,y.y,z.y,0,
                    x.z,y.z,z.z,0,
                    ex,ey,ez,1
                ]
            );
        }
        static lerp(left: Matrix, right: Matrix, v: number, out: Matrix)
        {
            for (var i = 0; i < 16; i++)
            {
                out.rawData[i] = left.rawData[i] * (1 - v) + right.rawData[i] * v;
            }
        }

        static transformVector3(vector: Vector3, transformation: Matrix, out: Vector3): void
        {
            var x = (vector.x * transformation.rawData[0]) + (vector.y * transformation.rawData[4]) + (vector.z * transformation.rawData[8]) + transformation.rawData[12];
            var y = (vector.x * transformation.rawData[1]) + (vector.y * transformation.rawData[5]) + (vector.z * transformation.rawData[9]) + transformation.rawData[13];
            var z = (vector.x * transformation.rawData[2]) + (vector.y * transformation.rawData[6]) + (vector.z * transformation.rawData[10]) + transformation.rawData[14];
            var w = (vector.x * transformation.rawData[3]) + (vector.y * transformation.rawData[7]) + (vector.z * transformation.rawData[11]) + transformation.rawData[15];

            out.x = x / w;
            out.y = y / w;
            out.z = z / w;
        }

        //变换向量
        static transformNormal(vector: Vector3, transformation: Matrix, out: Vector3): void
        {
            var x = (vector.x * transformation.rawData[0]) + (vector.y * transformation.rawData[4]) + (vector.z * transformation.rawData[8]);
            var y = (vector.x * transformation.rawData[1]) + (vector.y * transformation.rawData[5]) + (vector.z * transformation.rawData[9]);
            var z = (vector.x * transformation.rawData[2]) + (vector.y * transformation.rawData[6]) + (vector.z * transformation.rawData[10]);

            out.x = x;
            out.y = y;
            out.z = z;
        }
        static getVector3ByOffset(src: Matrix, offset: number, result: Vector3): void
        {
            result.x = src.rawData[offset];
            result.y = src.rawData[offset + 1];
            result.z = src.rawData[offset + 2];
        }


        static scaleByNum(value: number, mat: Matrix)
        {
            mat.rawData[0] *= value;
            mat.rawData[1] *= value;
            mat.rawData[2] *= value;
            mat.rawData[3] *= value;

            mat.rawData[4] *= value;
            mat.rawData[5] *= value;
            mat.rawData[6] *= value;
            mat.rawData[7] *= value;

            mat.rawData[8] *= value;
            mat.rawData[9] *= value;
            mat.rawData[10] *= value;
            mat.rawData[11] *= value;

            mat.rawData[12] *= value;
            mat.rawData[13] *= value;
            mat.rawData[14] *= value;
            mat.rawData[15] *= value;
        }

        static add(lhs: Matrix, rhs: Matrix, out: Matrix)
        {
            out.rawData[0] = lhs.rawData[0] + rhs.rawData[0];
            out.rawData[1] = lhs.rawData[1] + rhs.rawData[1];
            out.rawData[2] = lhs.rawData[2] + rhs.rawData[2];
            out.rawData[3] = lhs.rawData[3] + rhs.rawData[3];

            out.rawData[4] = lhs.rawData[4] + rhs.rawData[4];
            out.rawData[5] = lhs.rawData[5] + rhs.rawData[5];
            out.rawData[6] = lhs.rawData[6] + rhs.rawData[6];
            out.rawData[7] = lhs.rawData[7] + rhs.rawData[7];

            out.rawData[8] = lhs.rawData[8] + rhs.rawData[8];
            out.rawData[9] = lhs.rawData[9] + rhs.rawData[9];
            out.rawData[10] = lhs.rawData[10] + rhs.rawData[10];
            out.rawData[11] = lhs.rawData[11] + rhs.rawData[11];

            out.rawData[12] = lhs.rawData[12] + rhs.rawData[12];
            out.rawData[13] = lhs.rawData[13] + rhs.rawData[13];
            out.rawData[14] = lhs.rawData[14] + rhs.rawData[14];
            out.rawData[15] = lhs.rawData[15] + rhs.rawData[15];
        }

    }

}